package rexsee.network;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import rexsee.core.browser.Browser;
import rexsee.core.browser.clazz.JavascriptInterface;
import rexsee.core.utilities.Escape;
import rexsee.core.utilities.Json;
import rexsee.core.utilities.Utilities;
import rexsee.network.SocketAbstractClass.SocketConnectedListener;
import rexsee.network.SocketAbstractClass.SocketDataListener;
import rexsee.network.SocketAbstractClass.SocketFailedListener;
import rexsee.network.SocketAbstractClass.SocketLostListener;
import rexsee.network.SocketAbstractClass.SocketStartedListener;

public class RexseeSocket implements JavascriptInterface {

	public static final String INTERFACE_NAME = "Socket";
	@Override
	public String getInterfaceName() {
		return mBrowser.application.resources.prefix + INTERFACE_NAME;
	}
	@Override
	public JavascriptInterface getInheritInterface(Browser childBrowser) {
		return this;
	}
	@Override
	public JavascriptInterface getNewInterface(Browser childBrowser) {
		return new RexseeSocket(childBrowser);
	}

	private final SocketStartedListener onStarted = new SocketStartedListener() {
		@Override
		public void run(String id, boolean isServer) {
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_STARTED, new String[]{id, String.valueOf(isServer)});
		}
	};
	private final SocketFailedListener onFailed = new SocketFailedListener() {
		@Override
		public void run(String id, boolean isServer, String error) {
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_FAILED, new String[]{id, String.valueOf(isServer), error});
		}
	};
	private final SocketConnectedListener onConnected = new SocketConnectedListener() {
		@Override
		public void run(String id, long index) {
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_CONNECTED, new String[]{id, String.valueOf(index)});
		}
	};
	private final SocketLostListener onLost = new SocketLostListener() {
		@Override
		public void run(String id, long index, String message) {
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_LOST, new String[]{id, String.valueOf(index), message});
		}
	};
	private final SocketDataListener onRead = new SocketDataListener() {
		@Override
		public void run(String id, long index, byte[] bytes) {
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_READ, new String[]{id, String.valueOf(index), Escape.escape(new String(bytes))});
		}
	};
	private final SocketDataListener onWrite = new SocketDataListener() {
		@Override
		public void run(String id, long index, byte[] bytes) {
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_WRITE, new String[]{id, String.valueOf(index), Escape.escape(new String(bytes))});
		}
	};

	private final Browser mBrowser;
	private int mReadingCycle = 100;
	private int mConnectTimeout = 10000;

	private final HashMap<String, SocketServer> mServers;
	private final HashMap<String, SocketClient> mClients;

	public RexseeSocket(Browser browser) {
		mBrowser = browser;
		mServers = new HashMap<String, SocketServer>();
		mClients = new HashMap<String, SocketClient>();
		if (mBrowser != null) {
			mBrowser.eventList.add(SocketAbstractClass.EVENT_SOCKET_STARTED);
			mBrowser.eventList.add(SocketAbstractClass.EVENT_SOCKET_FAILED);
			mBrowser.eventList.add(SocketAbstractClass.EVENT_SOCKET_CONNECTED);
			mBrowser.eventList.add(SocketAbstractClass.EVENT_SOCKET_LOST);
			mBrowser.eventList.add(SocketAbstractClass.EVENT_SOCKET_READ);
			mBrowser.eventList.add(SocketAbstractClass.EVENT_SOCKET_WRITE);
		}
	}

	//JavaScript Interface

	public void setConnectTimeout(int milliseconds) {
		mConnectTimeout = milliseconds;
	}
	public int getConnectTimeout() {
		return mConnectTimeout;
	}
	public void setReadingCycle(int milliseconds) {
		mReadingCycle = milliseconds;
	}
	public int getReadingCycle() {
		return mReadingCycle;
	}

	public int getNumberOfServers() {
		return mServers.size();
	}
	public int getNumberOfClients() {
		return mClients.size();
	}
	public String getServerIds() {
		return Json.toJson(mServers.keySet());
	}
	public String getClientIds() {
		return Json.toJson(mClients.keySet());
	}
	public int getSocketNumberOfServer(String serverId) {
		SocketServer server = mServers.get(serverId);
		if (server == null) return 0;
		return server.count();
	}
	public String getSocketIndexesOfServer(String serverId) {
		SocketServer server = mServers.get(serverId);
		if (server == null) return "[]";
		return server.getSockets();
	}
	public boolean containsSocketInServer(String serverId, String index) {
		long socketIndex = Utilities.getLong(index, -1);
		if (socketIndex == -1) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Invalid socket index.");
			return false;
		}
		SocketServer server = mServers.get(serverId);
		if (server == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Server #" + serverId + " is not found.");
			return false;
		}
		return server.contains(socketIndex);
	}
	public boolean removeSocketFromServer(String serverId, String index) {
		long socketIndex = Utilities.getLong(index, -1);
		if (socketIndex == -1) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Invalid socket index.");
			return false;
		}
		SocketServer server = mServers.get(serverId);
		if (server == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Server #" + serverId + " is not found.");
			return false;
		}
		server.remove(socketIndex);
		return true;
	}
	public boolean removeSocketsFromServer(String serverId) {
		SocketServer server = mServers.get(serverId);
		if (server == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Server #" + serverId + " is not found.");
			return false;
		}
		server.remove();
		return true;
	}

	public boolean addServer(String serverId, int port, int backlog) {
		if (mServers.containsKey(serverId)) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Duplicated server id.");
			return false;
		}
		InetAddress addr = RexseeNetwork.getInetAddress();
		if (addr == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Local address is not available.");
			return false;
		}
		InetSocketAddress localAddress = new InetSocketAddress(addr, port);
		try {
			SocketServer server = new SocketServer(serverId, localAddress, backlog, -1, mReadingCycle);
			server.setListeners(onStarted, onFailed, onConnected, onLost, onRead, onWrite);
			mServers.put(serverId, server);
			server.start();
			return true;
		} catch (Exception e) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), e);
			return false;
		}
	}
	public boolean addClient(String clientId, int localPort, String remoteIp, int remotePort) {
		if (mClients.containsKey(clientId)) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Duplicated client id.");
			return false;
		}
		InetAddress addr = RexseeNetwork.getInetAddress();
		if (addr == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Local address is not available.");
			return false;
		}
		InetSocketAddress localAddress = new InetSocketAddress(addr, localPort);
		InetSocketAddress remoteAddress;
		try {
			remoteAddress = new InetSocketAddress(InetAddress.getByName(remoteIp), remotePort);
		} catch (UnknownHostException e) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Remote address is not available.");
			return false;
		}
		try {
			SocketClient client = new SocketClient(clientId, localAddress, remoteAddress, mConnectTimeout, mReadingCycle);
			client.setListeners(onStarted, onFailed, onConnected, onLost, onRead, onWrite);
			mClients.put(clientId, client);
			client.start();
			return true;
		} catch (Exception e) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), e);
			return false;
		}
	}
	public void removeServer(String serverId) {
		SocketServer server = mServers.get(serverId);
		if (server == null) return;
		server.close();
		server = null;
		mServers.remove(serverId);
	}
	public void removeServer() {
		Iterator<Entry<String, SocketServer>> iterator = mServers.entrySet().iterator();
		while (iterator.hasNext()) {
			try {
				SocketServer server = iterator.next().getValue();
				server.close();
				server = null;
			} catch (Exception e) {
			}
		}
		mServers.clear();
	}
	public void removeClient(String clientId) {
		SocketClient client = mClients.get(clientId);
		if (client == null) return;
		client.close();
		client = null;
		mClients.remove(clientId);
	}
	public void removeClient() {
		Iterator<Entry<String, SocketClient>> iterator = mClients.entrySet().iterator();
		while (iterator.hasNext()) {
			try {
				SocketClient client = iterator.next().getValue();
				client.close();
				client = null;
			} catch (Exception e) {
			}
		}
		mClients.clear();
	}

	public boolean writeToServer(String serverId, String index, String data) {
		long socketIndex = Utilities.getLong(index, -1);
		if (socketIndex == -1) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Invalid socket index.");
			return false;
		}
		SocketServer server = mServers.get(serverId);
		if (server == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Server #" + serverId + " is not found.");
			return false;
		}
		if (!server.contains(socketIndex)) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Socket #" + String.valueOf(socketIndex) + " is not found in Server #" + serverId + ".");
			return false;
		}
		return server.write(socketIndex, data.getBytes());
	}
	public boolean writeToClient(String clientId, String data) {
		SocketClient client = mClients.get(clientId);
		if (client == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Client #" + clientId + " is not found.");
			return false;
		}
		return client.write(data.getBytes());
	}

}
